﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Encryption;

namespace CryptoZip
{
	public static class CryptoZipLib
	{
		public const string CRYPTOZIP_EXTENSION = ".cryptozip";
		public const string IV_STRING = "ABCDEFGHIJKLMNOP"; //TODO change this to use GenerateIV
		public const string COMPRESSION_ALGORITHM = "Zip64";
		public const string ENCRYPTION_ALGORITHM = "AES 256-bit (Rijndael)";

		public static void Zip(FileInfo file, string key, ProgressChangedEventHandler progressChangedCallback)
		{
			Zip(file, key, file.DirectoryName, progressChangedCallback);
		}

		public static void Zip(FileInfo file, string key, string outputFolder, ProgressChangedEventHandler progressChangedCallback)
		{
			FileStream inputStream = null;
			FileStream outputStream = null;

			if (string.IsNullOrEmpty(outputFolder))
				outputFolder = file.DirectoryName;

			try
			{
				inputStream = File.OpenRead(file.FullName);
				outputStream = File.OpenWrite(outputFolder + "\\" + file.Name + CRYPTOZIP_EXTENSION);
				Aes aes = new AesManaged();
				byte[] keyBytes = Encoding.UTF8.GetBytes(key);
				byte[] ivBytes = Encoding.UTF8.GetBytes(IV_STRING);

				using (CryptoStream cryptoStream = new CryptoStream(outputStream, aes.CreateEncryptor(keyBytes, ivBytes), CryptoStreamMode.Write))
				{
					using (ZipOutputStream zipOutputStream = new ZipOutputStream(cryptoStream))
					{
						zipOutputStream.SetLevel(2);
						zipOutputStream.UseZip64 = UseZip64.On;
						ZipEntry zipEntry = new ZipEntry(file.Name);
						zipEntry.DateTime = DateTime.Now;

						zipOutputStream.PutNextEntry(zipEntry);
						int read = 0;
						byte[] buffer = new byte[4096];
						double totalRead = 0;
						double lastPercent = 0;

						do
						{
							read = inputStream.Read(buffer, 0, buffer.Length);
							zipOutputStream.Write(buffer, 0, read);

							totalRead += read;
							double percent = Math.Floor((totalRead / inputStream.Length) * 100);

							if (percent > lastPercent)
							{
								lastPercent = percent;

								if (progressChangedCallback != null)
									progressChangedCallback(new ProgressChangedEventArgs(percent, ProgressValueType.PercentCompleted));
							}

						} while (read == buffer.Length);

						zipOutputStream.Finish();
						zipOutputStream.Flush();
					}
				}
			}
			catch (Exception ex)
			{
				throw ex;
			}
			finally
			{
				inputStream.Close();
				outputStream.Close();
			}
		}

		public static void Unzip(FileInfo file, string key, ProgressChangedEventHandler progressChangedCallback)
		{
			Unzip(file, key, file.DirectoryName, progressChangedCallback);
		}

		public static void Unzip(FileInfo file, string key, string outputFolder, ProgressChangedEventHandler progressChangedCallback)
		{
			FileStream inputStream = null;

			if (string.IsNullOrEmpty(outputFolder))
				outputFolder = file.DirectoryName;

			try
			{
				inputStream = File.OpenRead(file.FullName);
				Aes aes = new AesManaged();
				byte[] keyBytes = Encoding.UTF8.GetBytes(key);
				byte[] ivBytes = Encoding.UTF8.GetBytes(IV_STRING);

				using (CryptoStream cryptoStream = new CryptoStream(inputStream, aes.CreateDecryptor(keyBytes, ivBytes), CryptoStreamMode.Read))
				{
					using (ZipInputStream zipInputStream = new ZipInputStream(cryptoStream))
					{
						ZipEntry zipEntry = zipInputStream.GetNextEntry();
						FileStream outputStream = File.OpenWrite(outputFolder + "\\" + zipEntry.Name);
						int read = 0;
						byte[] buffer = new byte[65536];
						double totalRead = 0;

						do
						{
							read = zipInputStream.Read(buffer, 0, buffer.Length);
							outputStream.Write(buffer, 0, read);

							totalRead += read;

							if (progressChangedCallback != null)
								progressChangedCallback(new ProgressChangedEventArgs(totalRead, ProgressValueType.BytesRead));
						} while (read > 0);

						zipInputStream.Close();
						outputStream.Flush();
						outputStream.Close();
					}
				}
			}
			catch (Exception ex)
			{
				throw ex;
			}
			finally
			{
				inputStream.Close();
			}
		}
	}

	public delegate void ProgressChangedEventHandler(ProgressChangedEventArgs args);

	public class ProgressChangedEventArgs : EventArgs
	{
		public double Value { get; set; }
		public ProgressValueType ValueType { get; set; }

		public ProgressChangedEventArgs() { }

		public ProgressChangedEventArgs(double value, ProgressValueType valueType)
		{
			Value = value;
			ValueType = valueType;
		}
	}

	public enum ProgressValueType
	{
		BytesRead,
		PercentCompleted
	}
}